#include "Game.h"
//#include <windows.h>
#include <Mmsystem.h>
#include <fstream>

#include <Support\TextDX.h>
#include <Support\String.h>

#include <Support\Log.h>
#include <Support\GAssert.h>
#include <Support\Config.h>
#include <Support\Storage.h>
#include <Support\Input.h>
#include <Support\Graphics\Graphics.h>
#include <Support\Audio.h>

#include <Support\Console.h>
#include <Support\MessageDialog.h>

std::ofstream	logBuffer;

Game::Game()
{
	paused = false;             // game is not paused
	fps = 100;
	fpsOn = false;              // default to fps display off
	initialized = false;
}
Game::~Game()
{
	deleteAll();                // free all reserved memory
	ShowCursor(true);           // show cursor
}
bool Game::initialize(HWND hw, const char* configurationFile,
	bool quiet)
{
	hwnd = hw;
	bool ret = true;
	clock.initialize();
	ret &= gLogger.initialize();
	ret &= gAssertManager.initialize(this);
	ret &= gConfig.initialize(configurationFile);
	ret &= gLogger.reDirect(gConfig.getOptionString("logfile"), &logBuffer);
	ret &= gStorageManager.initialize();
	ret &= gInput.initialize(hwnd, false);
	ret &= gGraphics.initialize(hwnd, GAME_WIDTH, GAME_HEIGHT, FULLSCREEN);
	ret &= (S_OK == gAudio.initialize(gConfig.getOptionString("wavebankfile"), gConfig.getOptionString("soundbankfile")));

	command = (String*)gStorageManager.New(sizeof(String));
	command->init();

	console = (Console*)gStorageManager.New(sizeof(Console));
	console->initialize();
	console->print("---Console---");


	messageDialog = (MessageDialog*)gStorageManager.New(sizeof(MessageDialog));
	messageDialog->initialize(hwnd);


	if (ret)
	{
		initialized = true;
		dxFont = (TextDX*)gStorageManager.New(sizeof(TextDX));
		dxFont->init();
		return true;
	}

	shutdown();
	return false;
}

bool Game::shutdown()
{
	if (NULL != messageDialog)
		gStorageManager.Delete(messageDialog);
	if (NULL != console)
		gStorageManager.Delete(console);
	gAudio.shutdown();
	gGraphics.shutdown();
	gInput.shutdown();
	gStorageManager.shutdown();
	gConfig.shutdown();
	gAssertManager.shutdown();
	gLogger.shutdown();
	clock.shutdown();
	return true;
}

void Game::terminate(int terminationCode)
{
	static const char* endMessage = "Abnormal Termination";
	gLog(endMessage);
	shutdown();
	exit(terminationCode);
	
}


//=============================================================================
// Window message handler
//=============================================================================
LRESULT Game::messageHandler(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	if (initialized)     // do not process messages if not initialized
	{
		switch (msg)
		{
		case WM_DESTROY:
			PostQuitMessage(0);        //tell Windows to kill this program
			return 0;
		case WM_KEYDOWN: case WM_SYSKEYDOWN:    // key down
			gInput.keyDown(wParam);
			return 0;
		case WM_KEYUP: case WM_SYSKEYUP:        // key up
			gInput.keyUp(wParam);
			return 0;
		case WM_CHAR:                           // character entered
			gInput.keyIn(wParam);
			return 0;
		case WM_MOUSEMOVE:                      // mouse moved
			gInput.mouseIn(lParam);
			return 0;
		case WM_INPUT:                          // raw mouse data in
			gInput.mouseRawIn(lParam);
			return 0;
		case WM_LBUTTONDOWN:                    // left mouse button down
			gInput.setMouseLButton(true);
			gInput.mouseIn(lParam);             // mouse position
			return 0;
		case WM_LBUTTONUP:                      // left mouse button up
			gInput.setMouseLButton(false);
			gInput.mouseIn(lParam);             // mouse position
			return 0;
		case WM_MBUTTONDOWN:                    // middle mouse button down
			gInput.setMouseMButton(true);
			gInput.mouseIn(lParam);             // mouse position
			return 0;
		case WM_MBUTTONUP:                      // middle mouse button up
			gInput.setMouseMButton(false);
			gInput.mouseIn(lParam);             // mouse position
			return 0;
		case WM_RBUTTONDOWN:                    // right mouse button down
			gInput.setMouseRButton(true);
			gInput.mouseIn(lParam);             // mouse position
			return 0;
		case WM_RBUTTONUP:                      // right mouse button up
			gInput.setMouseRButton(false);
			gInput.mouseIn(lParam);             // mouse position
			return 0;
		case WM_XBUTTONDOWN: case WM_XBUTTONUP: // mouse X button down/up
			gInput.setMouseXButton(wParam);
			gInput.mouseIn(lParam);             // mouse position
			return 0;
		case WM_MOUSEWHEEL:                     // mouse wheel move
			gInput.mouseWheelIn(wParam);
			return 0;
		case WM_DEVICECHANGE:                   // check for controller insert
			gInput.checkControllers();
			return 0;
		}
	}
	return DefWindowProc(hwnd, msg, wParam, lParam);    // let Windows handle it
}

//=============================================================================
// Toggle window or fullscreen mode
//=============================================================================
void Game::setDisplayMode(graphicsNS::DISPLAY_MODE mode)
{
	releaseAll();                   // free all user created surfaces
	gGraphics.changeDisplayMode(mode);
	resetAll();                     // recreate surfaces
}


//=============================================================================
// Call repeatedly by the main message loop in WinMain
//=============================================================================
void Game::run(HWND hwnd)
{
	// calculate elapsed time of last frame, save in frameTime
	clock.lap();
	frameTime = clock.lastLapTime();

	// Power saving code, requires winmm.lib
	// if not enough time has elapsed for desired frame rate
	if (frameTime < MIN_FRAME_TIME)
	{
		sleepTime = (DWORD)((MIN_FRAME_TIME - frameTime) * 1000);
		timeBeginPeriod(1);         // Request 1mS resolution for windows timer
		Sleep(sleepTime);           // release cpu for sleepTime
		timeEndPeriod(1);           // End 1mS timer resolution
		return;
	}

	if (frameTime > 0.0)
		fps = (fps*0.99f) + (0.01f / frameTime);  // average fps
	if (frameTime > MAX_FRAME_TIME) // if frame rate is very slow
		frameTime = MAX_FRAME_TIME; // limit maximum frameTime

	// update(), ai(), and collisions() are pure virtual functions.
	// These functions must be provided in the class that inherits from Game.
	if (!paused)                    // if not paused
	{
		update();                   // update all game items
		ai();                       // artificial intelligence
		collisions();               // handle collisions
		gInput.vibrateControllers(frameTime); // handle controller vibration
	}
	renderGame();                   // draw all game items

	//check for console key
	if (gInput.getCharIn() == CONSOLE_KEY)
	{
		gInput.clearCharIn();       // clear last char
		console->showHide();
		paused = console->getVisible(); // pause game when console is visible
	}
	consoleCommand();               // process user entered console command

	gInput.readControllers();       // read state of controllers

	messageDialog->update();
	//inputDialog->update();

	gAudio.run();                   // perform periodic sound engine tasks

	// if Alt+Enter toggle fullscreen/window
	if (gInput.isKeyDown(ALT_KEY) && gInput.wasKeyPressed(ENTER_KEY))
		setDisplayMode(graphicsNS::TOGGLE); // toggle fullscreen/window

	// if Esc key, set window mode
	if (gInput.isKeyDown(ESC_KEY))
		setDisplayMode(graphicsNS::WINDOW); // set window mode

	// if Pause key
	if (gInput.wasKeyPressed(VK_PAUSE))
		paused = !paused;

	// Clear input keys pressed
	// Call this after all key checks are done
	gInput.clear(inputNS::KEYS_PRESSED);
}

//=============================================================================
// Process console commands
// Override this function in the derived class if new console commands are added.
//=============================================================================
void Game::consoleCommand()
{
	command->set(console->getCommand().get());    // get command from console
	if (command->get() == "")                   // if no command
		return;

	if (command->get() == "help")              // if "help" command
	{
		console->print("Console Commands:");
		console->print("fps - toggle display of frames per second");
		return;
	}

	if (command->get() == "fps")
	{
		fpsOn = !fpsOn;                 // toggle display of fps
		if (fpsOn)
			console->print("fps On");
		else
			console->print("fps Off");
	}
}

void Game::releaseAll()
{
	//safeOnLostDevice(inputDialog);
	safeOnLostDevice(messageDialog);
	safeOnLostDevice(console);
	dxFont->onLostDevice();
	return;
}

void Game::resetAll()
{
	dxFont->onResetDevice();
	safeOnResetDevice(console);
	safeOnResetDevice(messageDialog);
	//safeOnResetDevice(inputDialog);
	return;
}

void Game::deleteAll()
{
	releaseAll();               // call onLostDevice() for every graphics item
	//safeDelete(audio);
	//safeDelete(graphics);
	//safeDelete(input);
	safeDelete(console);
	safeDelete(messageDialog);
	//safeDelete(inputDialog);
	initialized = false;
}

void Game::renderGame()
{
	const int BUF_SIZE = 20;
	static char buffer[BUF_SIZE];

	//start rendering
	if (SUCCEEDED(gGraphics.beginScene()))
	{
		render();           // call render() in derived object

		gGraphics.spriteBegin();    // begin drawing sprites
		if (fpsOn)           // if fps display requested
		{
			// convert fps to string
			_snprintf_s(buffer, BUF_SIZE, "fps %d ", (int)fps);
			dxFont->print(String(buffer), GAME_WIDTH - 100, GAME_HEIGHT - 28);
		}
		gGraphics.spriteEnd();      // end drawing sprites

		console->draw();    // console is drawn here so it appears on top of game

		messageDialog->draw();  // dialog is drawn on top
		//inputDialog->draw();    // dialog is drawn on top

		//stop rendering
		gGraphics.endScene();
	}
	handleLostGraphicsDevice();

	//display the back buffer on the screen
	gGraphics.showBackbuffer();
}


void Game::handleLostGraphicsDevice()
{
	// test for and handle lost device
	hr = gGraphics.getDeviceState();
	if (FAILED(hr))                  // if graphics device is not in a valid state
	{
		// if the device is lost and not available for reset
		if (hr == D3DERR_DEVICELOST)
		{
			Sleep(100);             // yield cpu time (100 mili-seconds)
			return;
		}
		// the device was lost but is now available for reset
		else if (hr == D3DERR_DEVICENOTRESET)
		{
			releaseAll();
			hr = gGraphics.reset(); // attempt to reset graphics device
			if (FAILED(hr))          // if reset failed
				return;
			resetAll();
		}
		else
			return;                 // other device error
	}
}